<?php
/**
 * 유틸리티 - 메일 전송
 *
 * SMTP서버 메일 전송(원격:socket function, 로컬:mail function)
 *
 * <code>
 * // 메일 파라미터 설정
 * $aMailParam = array();
 * // 메일 계정 - 사용자
 * $aMailParam['SMTPUser'] = 'cisample';
 * // 메일 계정 - 비밀번호
 * $aMailParam['SMTPPasswd'] = 'cisample';
 * // 보내는 사람 - 이메일
 * $aMailParam['mailFrom'] = 'cisample@ihko-01.cafe24test.com';
 * // 보내는 사람 - 이름
 * $aMailParam['fromName'] = 'CiServer';
 * // 받는 사람
 * $aMailParam['mailTo'] = array(array('email'=>'root@ihko-01.cafe24test.com', 'name'=>'이주영'));
 * // 제목
 * $aMailParam['subject'] = 'CI 테스트 - Local';
 * // 본문
 * $aMailParam['body'] = 'CiSample';
 * // 문자셋
 * $aMailParam['charset'] = 'UTF-8';
 * // 참조
 * $aMailParam['cc'] = 'root@ihko-01.cafe24test.com';
 * // 비밀참조
 * $aMailParam['bcc'] = 'root@ihko-01.cafe24test.com';
 * // 첨부
 * $aMailParam['attach'] = array(array('filename'=>$sAttactName, 'source'=>$sAttactFilePath));
 *
 * // 메일 전송
 * $bResult = utilMail::sendMail($aMailParam);
 * </code>
 *
 * @package util
 *
 * @author jylee3@simplexi.com
 * @version 1.0
 */
class utilMail
{
    /**
     * SMTP서버 메일 전송(원격:Socket function, 로컬:Mail function)
     *
     * @example utilMail::sendMail()
     *
     * @param array(
     *     'SMTPServer' => SMTP 서버주소
     *     'SMTPPort' => SMTP 포트
     *     'SMTPUser' => SMTP 로그인 Id
     *     'SMTPPasswd' => SMTP 로그인 비밀번호
     *     'connTimeout' => socket 연결 타임아웃(sec)
     *     'streamTimeout' => cmd push 타임아웃(sec)
     *     'mailFrom' => 보내는 사람 메일주소
     *     'fromName' => 보내는 사람 이름
     *     'mailTo' => array(array('email', 'name'), array('email', 'name'), ...) 받는사람 주소 및 이름
     *     'subject' => 메일 제목
     *     'body' => 메일 본문
     *     'charset' => 메일 캐릭터셋
     *     'cc' => cc
     *     'bcc' => bcc
     *     'attach' => array(array('filename', 'source'),array('filename', 'source')) 첨부파일 이름 및 첨부파일 경로
     *     'auto_iconv' => false(기본값 : true)
     *     'iconv_charset_in' => 변경할 원본 캐릭터셋 (기본 : FW_CHARSET 상수 값)
     *     'iconv_charset_out' => 변경할 변환 캐릭터셋 (기본 : UTF-8)
     * )
     * @return boolean
     */
    public static function sendMail($aMailParam)
    {
        return utilSplClass::load('utilMail')->send($aMailParam);
    }

    /**
     * SMTP Server 사용여부(전송 서버가 원격일 경우 SMTP 서버 사용)
     * @var String
     */
    private $bIsSMTPServer = false;

    /**
     * SMTP Server 도메인 또는 아이피
     * @var String
     */
    private $sSMTPServer;

    /**
     * SMTP Port
     * @var Integer
     */
    private $iSMTPPort = 25;

    /**
     * SMTP 계정 사용자
     * @var String
     */
    private $sSMTPUser;

    /**
     * SMTP 계정 비밀번호
     * @var String
     */
    private $sSMTPPasswd;

    /**
     * Mail Protocol - Header
     * @var Array
     */
    private $aMailHeader = array();

    /**
     * Mail Protocol - 보내는 사람
     * @var String
     */
    private $sMailFrom;

    /**
     * Mail Protocol - 회신 받을 주소
     * @var String
     */
    private $sReplyTo;

    /**
     * Mail Protocol - 받는 사람
     * @var String
     */
    private $aMailTo;

    /**
     * Mail Protocol - 메일 제목
     * @var String
     */
    private $sSubject;

    /**
     * Mail Protocol - 메일 본문
     * @var String
     */
    private $sMailBody;

    /**
     * Mail Protocol - 전송 문자셋
     * @var String
     */
    private $sCharset = 'UTF-8';

    /**
     * Mail Protocol - 첨부파일
     * @var Array
     */
    private $aAttach;

    /**
     * Mail Boundary
     * @var String
     */
    private $sBoundary;

    /**
     * Socket Resource
     * @var Resource
     */
    private $oSocket;

    /**
     * 연결 타임아웃
     * @var int
     */
    private $iTimeoutConn;

    /**
     * 스트림 전송 타임아웃
     * @var int
     */
    private $iTimeoutStream;

    /**
     * 프레임워크 내부 캐릭터셋과 메일 캐릭터셋이 다를 경우 자동으로 iconv 를 진행할 것인지 여부
     * @var boolean
     */
    private $isAutoIconv;

    /**
     * 자동 iconv 실행시 in, out 캐릭터셋 지정
     * @var string
     */
    private $sIconvCharsetIn;
    private $sIconvCharsetOut;


    /**
     * 생성자
     */
    public function __construct() {}

    /**
     * 메일발송
     */
    public function send($aMailParam)
    {
        $this->setBoundary();

        $this->setTimeout($aMailParam);

        $this->isAutoIconv = $aMailParam['auto_iconv'] === false ? false : true;

        if (empty($aMailParam['SMTPServer']) === false && empty($aMailParam['SMTPPort']) === false) {
            $this->setSMTPServer($aMailParam['SMTPServer'], $aMailParam['SMTPPort']);
        } else {
            $this->setSMTPServer();
        }

        if (empty($aMailParam['SMTPUser']) === false && empty($aMailParam['SMTPPasswd']) === false) {
            $this->setSMTPUser($aMailParam['SMTPUser'], $aMailParam['SMTPPasswd']);
        } else {
            $this->setSMTPUser();
        }

        if (empty($aMailParam['charset']) === false) {
            $this->setCharset($aMailParam['charset']);
        } else {
            $this->setCharset();
        }

        $this->sIconvCharsetIn = empty($aMailParam['iconv_charset_in']) === false ? $aMailParam['iconv_charset_in'] : FW_CHARSET;
        $this->sIconvCharsetOut = empty($aMailParam['iconv_charset_out']) === false ? $aMailParam['iconv_charset_out'] : $this->sCharset;

        $this->setMailFrom($aMailParam['mailFrom'], $aMailParam['fromName']);

        $this->setDefaultHeader();

        $this->setAddMailTo($aMailParam['mailTo']);
        $this->setSubject($aMailParam['subject']);
        $this->setMailBody($aMailParam['body']);

        if (empty($aMailParam['cc']) === false) {
            $this->setCc($aMailParam['cc']);
        }

        if (empty($aMailParam['bcc']) === false) {
            $this->setBcc($aMailParam['bcc']);
        }

        $this->setAttachFile($aMailParam['attach']);

        return $this->sendToMail();
    }

    /**
     * 메일전송
     */
    private function sendToMail()
    {
        if ($this->bIsSMTPServer === true) {
            return $this->smtpSend();
        }

        return $this->localSend();
    }

    /**
     * 타임아웃 설정
     * @param array $aMailParam
     */
    private function setTimeout($aMailParam)
    {
        $this->iTimeoutConn = $aMailParam['connTimeout'] ? (int)$aMailParam['connTimeout'] : 30;
        $this->iTimeoutStream = $aMailParam['streamTimeout'] ? (int)$aMailParam['streamTimeout'] : 5;
    }

    /**
     * Bound 세팅
     */
    private function setBoundary()
    {
        $this->sBoundary = md5(uniqid(microtime()));
    }

    /**
     * 문자열 세팅
     */
    private function setCharset($sCharset='UTF-8')
    {
        $this->sCharset = $sCharset;
    }

    /**
     * 보내는 메일 세팅
     */
    private function setMailFrom($sEmail, $sName = null)
    {
        $this->setReplyTo($sEmail);

        if (!empty($sName)) {
            if ($this->isIconvStr($sName) === true) {
                $sName = $this->iconvStr($sName);
            }

            $this->sMailFrom = '"=?'.$this->sCharset. '?B?' . base64_encode($sName).'?=" <'.$sEmail.'>';
        } else {
            $this->sMailFrom = $sEmail;
        }
    }

    /**
     * 회신받을 주소 세팅
     */
    private function setReplyTo($sEmail)
    {
        $this->sReplyTo = $sEmail;
    }

    /**
     * 제목 세팅
     */
    private function setSubject($sSubject)
    {
        if ($this->isIconvStr($sSubject) === true) {
            $sSubject = $this->iconvStr($sSubject);
        }

        $this->sSubject = "=?".$this->sCharset."?B?".base64_encode($sSubject)."?=";
    }

    /**
     * 본문 세팅
     */
    private function setMailBody($sContents)
    {
        if ($this->isIconvStr($sContents) === true) {
            $this->sMailBody = $this->iconvStr($sContents);
        } else {
            $this->sMailBody = $sContents;
        }
    }

    /**
     * 받는 사람 추가 배열을 받아 세팅함
     */
    private function setAddMailTo($aParam)
    {
        $this->aMailTo = array();

        foreach ($aParam as $aVal) {
            $this->setMailTo($aVal['email'], $aVal['name']);
        }
    }

    /**
     * 받는 사람 추가
     */
    private function setMailTo($sEmail, $sName = null)
    {
        $this->aMailTo[$sEmail] = $sName;
    }

    /**
     * 첨부파일
     */
    private function setAttachFile($aParam)
    {
        $this->aAttach = array();

        if (is_array($aParam)) {
            foreach ($aParam as $aVal) {
                $this->setAddAttach($aVal['filename'], $aVal['source']);
            }
        }

        /**
         * 첨부파일 처리
         */
        if (count($this->aAttach) > 0) {    /* 첨부파일이 존재할 경우 */
            $this->setAddHeader('MIME-Version', '1.0');
            $this->setAddHeader('Content-Type', 'Multipart/mixed; boundary = "'.$this->sBoundary.'"');
        } else {                            /* 첨부파일이 없을 경우 */
            $this->setAddHeader('Content-Type', 'text/html; charset=' . $this->sCharset);
            $this->setAddHeader('Content-Transfer-Encoding', '8bit');
        }
    }

    /**
     * 첨부파일 추가
     */
    private function setAddAttach($sFilename, $sSource)
    {
        $oHandle = fopen($sSource, 'r');

        if ($oHandle) {
            $sBody = fread($oHandle, filesize($sSource));
            @fclose($oHandle);

            $this->aAttach[$sFilename] = $sBody;
        }
    }

    /**
     * Cc header 세팅
     */
    private function setCc($sCc)
    {
        $this->setAddHeader('Cc', $sCc);
    }

    /**
     * Bcc header 세팅
     */
    private function setBcc($sBcc)
    {
        $this->setAddHeader('Bcc', $sBcc);
    }

    /**
     * 메일 헤더 추가
     */
    private function setAddHeader($sKey, $sValue)
    {
        $this->aMailHeader[$sKey] = $sValue;
    }

    /**
     * 받는 사람 형식
     */
    private function setSendMailTo($sEmail, $sName)
    {
        if ($this->isIconvStr($sName) === true) {
            $sName = $this->iconvStr($sName);
        }

        return (!empty($sName))? '"=?'.$this->sCharset. '?B?' . base64_encode($sName).'?=" <' .$sEmail. '>' : $sEmail;
    }

    /**
     * 메일 헤더 작성
     */
    private function setHeader()
    {
        $sHeader = '';

        foreach ($this->aMailHeader as $sKey => $sVal) {
            $sHeader .= $sKey.": ".$sVal."\r\n";
        }

        return $sHeader."\r\n";
    }

    /**
     * 메일 기본 헤더
     */
    private function setDefaultHeader()
    {
        $this->aMailHeader = array();

        $this->setAddHeader('From', $this->sMailFrom);
        $this->setAddHeader('User-Agent', 'Cafe24 Mail System');
        $this->setAddHeader('X-Accept-Language', 'ko, en');
        $this->setAddHeader('X-Sender', $this->sReplyTo);
        $this->setAddHeader('X-Mailer', 'PHP');
        $this->setAddHeader('X-Priority', 1 );
        $this->setAddHeader('Reply-to', $this->sReplyTo);
        $this->setAddHeader('Return-Path', $this->sReplyTo);
    }

    /**
     * 메일본문에 첨부파일 추가
     */
    private function setAttach()
    {
        $aResult = array();
        $sAttach = '';

        if (count($this->aAttach) > 0) {
            foreach ($this->aAttach as $sFilename => $sFileBody) {
                $sAttach = "--".$this->sBoundary."\r\n";
                $sAttach .= "Content-Type: application/octet-stream\r\n";
                $sAttach .= "Content-Transfer-Encoding: base64\r\n";
                $sAttach .= "Content-Disposition: attachment; filename=\"".$sFilename."\"\r\n\r\n";
                $sAttach .= $this->setEncodingContents($sFileBody)."\r\n\r\n";

                $aResult[] = $sAttach;
            }
        }

        return implode('', $aResult);
    }

    /**
     * 메일 본문 인코딩
     */
    private function setEncodingContents($sContets)
    {
        return chunk_split(base64_encode($sContets));
    }

    /**
     * 메일 본문 작성
     */
    private function setBody()
    {
        $sMailBody = "";

        if (count($this->aAttach) > 0) {    /* 첨부파일이 존재할 경우 */
            $sMailBody .= "--".$this->sBoundary."\r\n";
            $sMailBody .= "Content-Type: text/html; charset=".$this->sCharset."\r\n";
            $sMailBody .= "Content-Transfer-Encoding: base64\r\n\r\n";
            $sMailBody .= $this->setEncodingContents($this->sMailBody)."\r\n\r\n";
            $sMailBody .= "\r\n".$this->setAttach();
        } else {                            /* 첨부파일이 없을 경우 */
            $sMailBody = $this->sMailBody;
        }

        return $sMailBody;
    }


    /**
     * SMTP 서버 및 포트 세팅
     */
    private function setSMTPServer($sSMTPServer = 'localhost', $iPort = 25)
    {
        if ($sSMTPServer != null) {
            $this->bIsSMTPServer = true;
            $this->iSMTPPort = $iPort;
            $this->sSMTPServer = $sSMTPServer;
        }
    }

    /**
     * SMTP 사용자 정보
     */
    private function setSMTPUser($sUser='', $sPasswd='')
    {
        $this->sSMTPUser = $sUser;
        $this->sSMTPPasswd = $sPasswd;
    }

    /**
     * 외부 SMTP 서버를 이용하여 전송
     */
    private function smtpSend()
    {
        $bIsSuccess = false;

        $this->setAddHeader('Subject', $this->sSubject);

        $sMailBody = $this->setBody();

        if (count($this->aMailTo) > 0) {
            foreach ($this->aMailTo as $sEmail=>$sName) {

                $sMailTo = $this->setSendMailTo($sEmail, $sName);

                $this->setAddHeader('To', $sMailTo);

                /* 메일헤더와 본문을 이용해 전송할 메일을 생성 */
                $sContents = $this->setHeader()."\r\n".$sMailBody;

                $this->oSocket = fsockopen($this->sSMTPServer, $this->iSMTPPort, $errno, $errstr, $this->iTimeoutConn);

                if ($this->oSocket) {
                    $this->setPush('HELO '.$this->sSMTPServer);

                    if ($this->sSMTPUser) {
                        $this->setPush('AUTH LOGIN');
                        $this->setPush(base64_encode($this->sSMTPUser));
                        $this->setPush(base64_encode($this->sSMTPPasswd));
                    }

                    $this->setPush('MAIL FROM: '.$this->sReplyTo);
                    $this->setAddHeader('MAIL FROM', $this->sMailFrom);
                    $this->setPush('RCPT To:'.$sEmail);
                    $this->setPush('DATA');
                    $this->setPush($sContents);

                    $sResult = $this->setPush('.');

                    if (strpos($sResult, '250') !== false) {
                        $bIsSuccess = true;
                    }

                    $this->setPush('QUIT');
                }

                socket_close($this->oSocket);
                $this->oSocket = null;
            }
        }

        return $bIsSuccess;
    }

    /**
     * 내용전송 및 결과값
     */
    private function setPush($sContents)
    {
        fputs($this->oSocket, $sContents . "\r\n");

        stream_set_timeout($this->oSocket, $this->iTimeoutStream);
        $aStatus = stream_get_meta_data($this->oSocket);

        if (!$aStatus['timed_out'] && !feof($this->oSocket)) {
            return fgets($this->oSocket, 512);
        }

        return false;
    }

    /**
     * 로컬 서버를 이용하여 전송
     */
    private function localSend()
    {
        $bIsSuccess = false;

        $sContents = $this->setBody();

        foreach ($this->aMailTo as $sEmail => $sName) {
            $sToMail = $this->setSendMailTo($sEmail, $sName);
            $sHeader = $this->setHeader();

            if (mail($sToMail, $this->sSubject, $sContents, $sHeader)) {
                $bIsSuccess = true;
            }
        }

        return $bIsSuccess;
    }

    private function isIconvStr($sVal)
    {
        if ($this->isAutoIconv !== true) {
            return false;
        }

        if (empty($sVal) === true) {
            return false;
        }

        if (strcasecmp($this->sIconvCharsetIn, $this->sIconvCharsetOut) === 0) {
            return false;
        }

        return true;
    }

    private function iconvStr($sVal)
    {
        $sVal = iconv($this->sIconvCharsetIn, $this->sIconvCharsetOut.'//IGNORE//TRANSLIT', $sVal);

        return $sVal;
    }
}
